import json
import os
import time
import psutil
import requests
import shutil
import threading
import subprocess
import sys
from logger import log

if getattr(sys, 'frozen', False):
    BASE_DIR = os.path.dirname(sys.executable)
else:
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def get_task_configs():
    """从 config.json 文件中读取所有任务配置。"""
    with open(os.path.join(BASE_DIR, 'config.json'), 'r', encoding='utf-8') as f:
        config = json.load(f)
    return config.get('tasks', [])


class Updater:
    def __init__(self, config):
        self.code = config['code']
        self.exec_name = config['exec']
        self.args = config.get('args', [])
        self.version_url = config['version_url']
        self.download_url = config['download_url']
        self.listen_port = config['listen_port']

    def _get_exec_path(self):
        """获取可执行文件路径。"""
        client_dir = os.path.join(BASE_DIR, 'clients', self.code)
        return os.path.join(client_dir, self.exec_name)

    def stop(self):
        """强制停止正在运行的程序。"""
        exec_path = self._get_exec_path()

        for proc in psutil.process_iter(['pid', 'name', 'exe']):
            try:
                if proc.info['name'] == self.exec_name and proc.info['exe'] == exec_path:
                    log.info(f"正在强制停止进程 {proc.info['name']} (PID: {proc.info['pid']})")
                    p = psutil.Process(proc.info['pid'])
                    p.terminate()
                    p.wait()
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
            except Exception as e:
                raise Exception(f"强制停止进程 {proc.info['pid']} 时出错: {e}")

    def start(self, exec_path):
        """启动程序。"""
        log.info(f"正在启动 {exec_path} with args: {self.args}, cwd: {os.path.dirname(exec_path)}")
        try:
            exec_dir = os.path.dirname(exec_path)
            
            start_args = self.args.copy()
            start_args.append(f'--listen_port={self.listen_port}')
            subprocess.Popen([exec_path] + start_args, cwd=exec_dir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            log.info(f"{exec_path} 已启动")
        except FileNotFoundError:
            raise Exception(f"可执行文件未找到: {exec_path}")
        except Exception as e:
            raise Exception(f"启动 {exec_path} 时出错: {e}")

    def download_and_unzip(self, version):
        """下载并解压指定版本的程序。"""
        try:
            url = self.download_url.format(version=version)
            file_name = url.split('/')[-1]
            download_path = os.path.join(BASE_DIR, 'clients', self.code, file_name)

            log.info(f"开始下载新版本: {url}")
            with requests.get(url, stream=True) as r:
                r.raise_for_status()
                os.makedirs(os.path.dirname(download_path), exist_ok=True)
                with open(download_path, 'wb') as f:
                    shutil.copyfileobj(r.raw, f)
            log.info(f"新版本下载完成: {download_path}")

            log.info(f"新版本 {version} 已准备就绪，停止程序，替换程序")
            self.stop()

            extract_dir = os.path.join(BASE_DIR, 'clients', self.code)
            log.info(f"解压到 {extract_dir}")
            shutil.unpack_archive(download_path, extract_dir)
            log.info("解压完成")
        except requests.RequestException as e:
            raise Exception(f"下载失败: {e}")
        except Exception as e:
            raise Exception(f"处理下载文件时出错: {e}")

    def get_latest_version(self):
        """从服务器获取最新版本号。"""
        response = requests.get(self.version_url)
        response.raise_for_status()
        return response.text.strip()

    def get_local_version(self):
        """获取本地程序的版本号和状态。"""
        try:
            response = requests.get(f"http://127.0.0.1:{self.listen_port}/info")
            response.raise_for_status()
            data = response.json()
            return data.get("version", "0.0.0"), data.get("status", "unknown")
        except requests.RequestException as e:
            log.error(f"获取 {self.code} 版本和状态失败: {e}")
            return "0.0.0", "error"

    def is_running(self):
        """检查程序是否正在运行。"""
        exec_path = self._get_exec_path()

        for proc in psutil.process_iter(['pid', 'name', 'exe']):
            try:
                if proc.info['name'] == self.exec_name and proc.info['exe'] == exec_path:
                    log.info(f"进程 {proc.info['name']} (PID: {proc.info['pid']}) 正在运行")
                    return True
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
            except Exception as e:
                log.error(f"检查进程 {proc.info['pid']} 时出错: {e}")
        return False

    def run(self):
        """包含守护进程的主要逻辑。"""
        log.info(f"任务 {self.code} 启动")

        while True:
            try:
                latest_version = self.get_latest_version()
                exec_to_start = self._get_exec_path()
                # 首先检查是否正在运行，可以启动，但是可能都没有可执行文件，需要下载
                if not self.is_running():
                    # 如果未运行，检查是否有可执行文件
                    log.info(f"{self.code} 未在运行，获取最新版本号，检查是否有可执行文件。")

                    if not exec_to_start or not os.path.exists(exec_to_start):                        
                        log.info(f"未找到 {self.code} 的可执行文件。")
                        self.download_and_unzip(latest_version)
                        os.chmod(exec_to_start, 0o755)

                    log.info(f"存在 {self.code} 可执行文件，尝试启动。")
                    self.start(exec_to_start)
                else:
                    local_version, status = self.get_local_version()
                    log.info(f"最新版本: {latest_version}, 本地版本: {local_version}, 状态: {status}")

                    if latest_version and latest_version != local_version:
                        if status != 'idle':
                            log.info(f"任务 {self.code} 正在运行中，状态为 {status}，本次不更新")
                            time.sleep(30)
                            continue

                        log.info(f"发现新版本 {latest_version}，当前版本 {local_version}。")
                        self.download_and_unzip(latest_version)
                        os.chmod(exec_to_start, 0o755)
                        log.info(f"更新 {self.code} 到版本 {latest_version} 成功。准备启动新版本...")
                        if not self.is_running():
                            self.start(exec_to_start)
                        else:
                            log.warning(f"任务 {self.code} 正在运行中，状态为 {status}，本次不更新")
                    else:
                        log.info("当前已是最新版本。")
            except Exception as e:
                log.error(f"An error occurred in run loop: {e}")
            time.sleep(30)


def main():
    """主函数，为每个任务创建一个线程。"""
    try:
        tasks = get_task_configs()
        if not tasks:
            log.warning("在 config.json 中未找到任何任务。")
            return

        threads = []
        for task_config in tasks:
            updater = Updater(task_config)
            thread = threading.Thread(target=updater.run)
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()
    except Exception as e:
        log.error(f"An error occurred in main: {e}")

if __name__ == "__main__":
    main()